package com.own.component.util.math.util;

import com.own.component.util.math.exception.MathException;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * FormulaCalculateUtil
 *
 * @author chenxueli
 * @date 2022-06-15 03:14:15
 */
@Slf4j
public class FormulaCalculateUtil<T> extends AbstractCalculateUtil<String, T> {

    private static final char ADD_SYMBOL = '+';
    private static final char SUBTRACT_SYMBOL = '-';
    private static final char MULTIPLY_SYMBOL = '*';
    private static final char DIVIDE_SYMBOL = '/';
    private static final char POWER_SYMBOL = '^';

    private static final String PARENTHESIS_PREFIX = "(";
    private static final String PARENTHESIS_SUFFIX = ")";
    private static final String STRING_ADD_SYMBOL = "+";
    private static final String STRING_SUBTRACT_SYMBOL = "-";
    private static final String STRING_MULTIPLY_SYMBOL = "*";
    private static final String STRING_DIVIDE_SYMBOL = "/";
    private static final String STRING_POWER_SYMBOL = "^";

    /**
     * 浮点计算
     *
     * @param number1  数字1
     * @param number2  数字2
     * @param operator 计算符号
     * @return 计算后的数据
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    private static BigDecimal doubleCalculate(BigDecimal number1, BigDecimal number2, char operator) {
        return switch (operator) {
            case ADD_SYMBOL -> number1.add(number2);
            case SUBTRACT_SYMBOL -> number1.subtract(number2);
            case MULTIPLY_SYMBOL -> number1.multiply(number2);
            case DIVIDE_SYMBOL -> number1.divide(number2, 15, RoundingMode.HALF_UP);
            case POWER_SYMBOL -> BigDecimal.valueOf(Math.pow(number1.doubleValue(), number2.doubleValue()));
            default -> throw new MathException("运算符异常！");
        };
    }

    /**
     * 优先级
     *
     * @param c 内容
     * @return 优先级序号
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    private static int priority(String c) {
        if (c == null) {
            return 0;
        }
        return switch (c) {
            // 括号
            case PARENTHESIS_PREFIX -> 1;
            // 加减算法
            case STRING_ADD_SYMBOL -> 2;
            case STRING_SUBTRACT_SYMBOL -> 2;
            // 乘除算法
            case STRING_MULTIPLY_SYMBOL -> 3;
            case STRING_DIVIDE_SYMBOL -> 3;
            // 幂运算
            case STRING_POWER_SYMBOL -> 4;
            default -> {
                throw new MathException("运算符异常！");
            }
        };
    }

    /**
     * 计算
     *
     * @param expression 表达式信息
     * @return 值信息
     * @author chenxueli
     * @date 2022-06-15 03:14:15
     */
    @Override
    @SuppressWarnings("all")
    public String calculate(String expression) {
        // 数字栈
        var number = new Stack<BigDecimal>();
        // 符号栈
        var operator = new Stack<String>();
        // 在栈顶压人一个null，配合它的优先级，目的是减少下面程序的判断
        operator.push(null);
        // 将expr打散为运算数和运算符
        var regex = "(?<!\\d)-?\\d+(\\.\\d+)?|[+\\-*/\\^()]";
        // 这个正则为匹配表达式中的数字或运算符
        var pattern = Pattern.compile(regex);
        var matcher = pattern.matcher(expression);
        while (matcher.find()) {
            var temp = matcher.group();
            // 遇到符号
            if (temp.matches("[+\\-*/\\^()]")) {
                // 遇到左括号，直接入符号栈
                if (temp.equals(PARENTHESIS_PREFIX)) {
                    operator.push(temp);
                }
                // 遇到右括号，"符号栈弹栈取栈顶符号b，数字栈弹栈取栈顶数字a1，数字栈弹栈取栈顶数字a2，计算a2 b a1 ,将结果压入数字栈"，重复引号步骤至取栈顶为左括号，将左括号弹出
                else if (temp.equals(PARENTHESIS_SUFFIX)) {
                    String b;
                    while (!(b = operator.pop()).equals(PARENTHESIS_PREFIX)) {
                        var a1 = number.pop();
                        var a2 = number.pop();
                        number.push(doubleCalculate(a2, a1, b.charAt(0)));
                    }
                }
                // 遇到运算符，满足该运算符的优先级大于栈顶元素的优先级压栈；否则计算后压栈
                else {
                    while (priority(temp) <= priority(operator.peek())) {
                        var a1 = number.pop();
                        var a2 = number.pop();
                        String b = operator.pop();
                        number.push(doubleCalculate(a2, a1, b.charAt(0)));
                    }
                    operator.push(temp);
                }
            }
            // 遇到数字，直接压入数字栈
            else {
                number.push(new BigDecimal(temp));
            }
        }
        // 遍历结束后，符号栈数字栈依次弹栈计算，并将结果压入数字栈
        while (operator.peek() != null) {
            var a1 = number.pop();
            var a2 = number.pop();
            String b = operator.pop();
            number.push(doubleCalculate(a2, a1, b.charAt(0)));
        }
        // 科学计数
        var pop = number.pop();
        var format = NumberFormat.getInstance();
        format.setGroupingUsed(false);
        // 科学计数转换成完全显示
        return format.format(pop);
    }
}
